/*
 * Copyright (c) 2011, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.protwarrior;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import yawning.mew.ProcEffect;
import yawning.mew.Report;
import yawning.mew.util.CSVDumper;

public class ProtWarriorReport extends Report {
	public enum ProtWarriorStat implements IStat {
		DEMORALIZING_SHOUT_UPTIME,
		SHIELD_BLOCK_UPTIME,
		TIER_12_4PC_UPTIME,
		TIER_13_2PC_UPTIME,
		TIER_13_2PC_ABSORBED,
		TIER_13_2PC_AVG_SZ,
		TIER_13_2PC_PROCS,
		TIER_13_2PC_PROC_INTERVAL,
		SHIELD_WALL_UPTIME,
		LAST_STAND_UPTIME,
		ENRAGED_REGEN_UPTIME,
		ENRAGED_REGEN_HPS,
		ENRAGED_REGEN_AVG_SZ,
		BLOOD_CRAZE_UPTIME,
		BLOOD_CRAZE_HPS,
		BLOOD_CRAZE_AVG_SZ,
		VICTORY_RUSH_HPS,
		HOLD_THE_LINE_UPTIME,
		PHYSICAL_DAMAGE_REDUCTION,
		MAGIC_DAMAGE_REDUCTION,

		TOTAL_DTPS,
		TOTAL_DAMAGE_TAKEN,

		TOTAL_DAMAGE,
		TOTAL_DAMAGE_BASE,

		MOB_WHITE_ATTACK_DTPS,
		MOB_WHITE_ATTACKS,
		P_MOB_WHITE_MISS,
		P_MOB_WHITE_DODGE,
		P_MOB_WHITE_PARRY,
		P_MOB_WHITE_CRIT_BLOCK,
		P_MOB_WHITE_BLOCK,
		P_MOB_WHITE_CRIT,
		P_MOB_WHITE_HIT,
		MOB_WHITE_INTERVAL,
		MOB_WHITE_AVG_DMG,

		MOB_YELLOW_ATTACK_DTPS,
		MOB_YELLOW_ATTACKS,
		P_MOB_YELLOW_MISS,
		P_MOB_YELLOW_DODGE,
		P_MOB_YELLOW_PARRY,
		P_MOB_YELLOW_CRIT_BLOCK,
		P_MOB_YELLOW_BLOCK,
		P_MOB_YELLOW_HIT,
		MOB_YELLOW_INTERVAL,
		MOB_YELLOW_AVG_DMG,

		MOB_MAGIC_ATTACK_DTPS,
		MOB_MAGIC_ATTACKS,
		P_MOB_MAGIC_MISS,
		P_MOB_MAGIC_HIT,
		MOB_MAGIC_INTERVAL,
		MOB_MAGIC_AVG_DMG,

		NR_PUMMELS,
		NR_GAG_ORDERS,
		NR_INTERRUPTED_CASTS,
		NR_TARGET_CASTS,

		SUNDER_ARMOR_UPTIME,
		BASTION_OF_DEFENSE_UPTIME,
		INNER_RAGE_UPTIME,
		RECKLESSNESS_UPTIME,
		RETALIATION_UPTIME,
		RUDE_INTERRUPTION_UPTIME,
		TIER_12_DPS_2PC_UPTIME,
		HEROISM_UPTIME,
		UNHOLY_FRENZY_UPTIME,
		TOTT_UPTIME,
		SHATTERING_THROW_UPTIME,

		VENGEANCE_CAP,
		VENGEANCE_AVG,

		P_RAGE_TPS,
		P_WHITE_ATTACK_TPS,
		P_DEEP_WOUNDS_TPS,
		P_SHIELD_SLAM_TPS,
		P_DEVASTATE_TPS,
		P_REVENGE_TPS,
		P_THUNDER_CLAP_TPS,
		P_REND_TPS,
		P_CONCUSSION_BLOW_TPS,
		P_SHOCKWAVE_TPS,
		P_HEROIC_THROW_TPS,
		P_VICTORY_RUSH_TPS,
		P_HEROIC_STRIKE_TPS,
		P_CLEAVE_TPS,
		P_RETALIATION_TPS,
		P_COMBUST_TPS,
		P_OTHER_TPS,

		RAGE_GAIN_TPS,
		RAGE_GAINED,
		RAGE_GAINED_AUTO_ATTACK,
		RAGE_GAINED_BERSERKER_RAGE,
		RAGE_GAINED_CHARGE,
		RAGE_GAINED_SHOUT,					// Commanding Shout + Battle Shout
		RAGE_GAINED_SHIELD_SPECIALIZATION,
		RAGE_GAINED_MOB_WHITE,
		RAGE_GAINED_MOB_YELLOW,
		RAGE_GAINED_MOB_MAGIC,
		RAGE_SPENT,
		RAGE_OVERFLOW,
		BATTLE_TRANCE_PROCS,
		BATTLE_TRANCE_AVG_RAGE_RETURN,

		WHITE_ATTACK_TPS,
		WHITE_ATTACK_DPS,
		WHITE_ATTACKS,
		P_WHITE_ATTACK_MISS,
		P_WHITE_ATTACK_DODGE,
		P_WHITE_ATTACK_PARRY,
		P_WHITE_ATTACK_GLANCE,
		P_WHITE_ATTACK_CRIT,
		P_WHITE_ATTACK_HIT,
		WHITE_ATTACK_INTERVAL,
		WHITE_ATTACK_AVG_DMG,

		DEEP_WOUNDS_TPS,
		DEEP_WOUNDS_DPS,
		DEEP_WOUNDS_TICKS,
		DEEP_WOUNDS_AVG_DMG,
		DEEP_WOUNDS_AVG_THREAT,
		DEEP_WOUNDS_ROLLED,
		DEEP_WOUNDS_ROLLED_DMG,
		DEEP_WOUNDS_MUNCHED,
		DEEP_WOUNDS_MUNCHED_DMG,
		DEEP_WOUNDS_REFRESH_LOST,
		DEEP_WOUNDS_REFRESH_LOST_DMG,

		SHIELD_SLAM_TPS,
		SHIELD_SLAM_DPS,
		SHIELD_SLAM_TPR,
		SHIELD_SLAMS,
		P_SHIELD_SLAM_MISS,
		P_SHIELD_SLAM_DODGE,
		P_SHIELD_SLAM_PARRY,
		P_SHIELD_SLAM_CRIT,
		P_SHIELD_SLAM_HIT,
		SHIELD_SLAM_AVG_DMG,
		SHIELD_SLAM_AVG_THREAT,

		SWORD_AND_BOARD_PROCS,
		SWORD_AND_BOARD_PROC_SHIELD_SLAMS,
		SWORD_AND_BOARD_PROCS_WASTED,

		DEVASTATE_TPS,
		DEVASTATE_DPS,
		DEVASTATE_TPR,
		DEVASTATES,
		P_DEVASTATE_MISS,
		P_DEVASTATE_DODGE,
		P_DEVASTATE_PARRY,
		P_DEVASTATE_CRIT,
		P_DEVASTATE_HIT,
		DEVASTATE_AVG_DMG,
		DEVASTATE_AVG_THREAT,

		REVENGE_TPS,
		REVENGE_DPS,
		REVENGE_TPR,
		REVENGES,
		P_REVENGE_MISS,
		P_REVENGE_DODGE,
		P_REVENGE_PARRY,
		P_REVENGE_CRIT,
		P_REVENGE_HIT,
		REVENGE_AVG_DMG,
		REVENGE_AVG_THREAT,

		THUNDER_CLAP_TPS,
		THUNDER_CLAP_DPS,
		THUNDER_CLAP_TPR,
		THUNDER_CLAPS,
		P_THUNDER_CLAP_MISS,
		P_THUNDER_CLAP_CRIT,
		P_THUNDER_CLAP_HIT,
		THUNDER_CLAP_AVG_DMG,
		THUNDER_CLAP_AVG_THREAT,
		THUNDER_CLAP_UPTIME,

		REND_TPS,
		REND_DPS,
		REND_DIRECT_TPS,
		REND_DIRECT_DPS,
		REND_TPR,
		RENDS,
		P_REND_MISS,
		P_REND_DODGE,
		P_REND_PARRY,
		P_REND_CRIT,
		P_REND_HIT,
		REND_AVG_DMG,
		REND_AVG_THREAT,
		REND_DOT_DPS,
		REND_DOT_TPS,
		REND_TICKS,
		P_REND_DOT_CRIT,
		P_REND_DOT_HIT,
		REND_DOT_AVG_DMG,
		REND_DOT_AVG_THREAT,
		REND_UPTIME,

		CONCUSSION_BLOW_TPS,
		CONCUSSION_BLOW_DPS,
		CONCUSSION_BLOW_TPR,
		CONCUSSION_BLOWS,
		P_CONCUSSION_BLOW_MISS,
		P_CONCUSSION_BLOW_DODGE,
		P_CONCUSSION_BLOW_PARRY,
		P_CONCUSSION_BLOW_CRIT,
		P_CONCUSSION_BLOW_HIT,
		CONCUSSION_BLOW_AVG_DMG,
		CONCUSSION_BLOW_AVG_THREAT,

		SHOCKWAVE_TPS,
		SHOCKWAVE_DPS,
		SHOCKWAVE_TPR,
		SHOCKWAVES,
		P_SHOCKWAVE_MISS,
		P_SHOCKWAVE_CRIT,
		P_SHOCKWAVE_HIT,
		SHOCKWAVE_AVG_DMG,
		SHOCKWAVE_AVG_THREAT,

		VICTORY_RUSH_TPS,
		VICTORY_RUSH_DPS,
		VICTORY_RUSHS,
		P_VICTORY_RUSH_MISS,
		P_VICTORY_RUSH_DODGE,
		P_VICTORY_RUSH_PARRY,
		P_VICTORY_RUSH_CRIT,
		P_VICTORY_RUSH_HIT,
		VICTORY_RUSH_AVG_DMG,
		VICTORY_RUSH_AVG_HEAL,
		VICTORY_RUSH_AVG_THREAT,
		IMPENDING_VICTORY_PROCS,
		IMPENDING_VICTORY_PROCS_WASTED,

		HEROIC_THROW_TPS,
		HEROIC_THROW_DPS,
		HEROIC_THROWS,
		P_HEROIC_THROW_MISS,
		P_HEROIC_THROW_CRIT,
		P_HEROIC_THROW_HIT,
		HEROIC_THROW_AVG_DMG,
		HEROIC_THROW_AVG_THREAT,

		HEROIC_STRIKE_TPS,
		HEROIC_STRIKE_DPS,
		HEROIC_STRIKE_TPR,
		HEROIC_STRIKES,
		P_HEROIC_STRIKE_MISS,
		P_HEROIC_STRIKE_DODGE,
		P_HEROIC_STRIKE_PARRY,
		P_HEROIC_STRIKE_CRIT,
		P_HEROIC_STRIKE_HIT,
		HEROIC_STRIKE_AVG_DMG,
		HEROIC_STRIKE_AVG_THREAT,

		CLEAVE_TPS,
		CLEAVE_DPS,
		CLEAVE_TPR,
		CLEAVES,
		P_CLEAVE_MISS,
		P_CLEAVE_DODGE,
		P_CLEAVE_PARRY,
		P_CLEAVE_CRIT,
		P_CLEAVE_HIT,
		CLEAVE_AVG_DMG,
		CLEAVE_AVG_THREAT,

		RETALIATION_TPS,
		RETALIATION_DPS,
		RETALIATIONS,
		P_RETALIATION_MISS,
		P_RETALIATION_DODGE,
		P_RETALIATION_PARRY,
		P_RETALIATION_CRIT,
		P_RETALIATION_HIT,
		RETALIATION_AVG_DMG,
		RETALIATION_AVG_THREAT,

		COMBUST_TPS,
		COMBUST_DPS,
		COMBUST_TICKS,
		COMBUST_AVG_DMG,
		COMBUST_AVG_THREAT,
		COMBUST_ROLLED,
		COMBUST_ROLLED_DMG,
		COMBUST_MUNCHED,
		COMBUST_MUNCHED_DMG,
		COMBUST_REFRESH_LOST,
		COMBUST_REFRESH_LOST_DMG,

		OTHER_TPS,
		OTHER_DPS,
		OTHER_HPS,
	}

	boolean mShouldReportMitigation = false;
	boolean mHasValidMobWhiteDamage = true;
	boolean mHasValidMobYellowDamage = true;
	boolean mHasValidMobMagicDamage = true;
	boolean mHasValidRageOverflow = true;

	Map<ProcEffect, Double> mProcUptimes;	// Proc uptimes

	// Data dumpers I use to generate graphs.
	boolean mDumpVengeance = false;
	boolean mDumpWhiteDamage = false;
	boolean mDumpYellowDamage = false;
	boolean mDumpMagicDamage = false;

	String mConfig;
	double[] mVengeanceSamples;				// Vengeance
	double[] mWhiteDamageSamples;			// White Damage Taken
	double[] mYellowDamageSamples;			// Yellow Damage Taken
	double[] mMagicDamageSamples;			// Magic Damage Taken

	List<double[]> mVengeanceSampleList;
	List<double[]> mWhiteDamageSampleList;
	List<double[]> mYellowDamageSampleList;
	List<double[]> mMagicDamageSampleList;

	@Override
	public Report accumulate(Report r) {
		super.accumulate(r);

		ProtWarriorReport br = (ProtWarriorReport) r;

		if (br.mShouldReportMitigation == true) mShouldReportMitigation = true;
		if (br.mHasValidMobWhiteDamage == false) mHasValidMobWhiteDamage = false;
		if (br.mHasValidMobYellowDamage == false) mHasValidMobYellowDamage = false;
		if (br.mHasValidMobMagicDamage == false) mHasValidMobMagicDamage = false;
		if (br.mHasValidRageOverflow == false) mHasValidRageOverflow = false;

		if (mProcUptimes == null) {
			// XXX: Should I copy this?
			mProcUptimes = br.mProcUptimes;
		} else {
			if (br.mProcUptimes != null && br.mProcUptimes.isEmpty() == false) {
				for (Map.Entry<ProcEffect, Double> entry : br.mProcUptimes.entrySet()) {
					ProcEffect proc = entry.getKey();
					Double newUptime = entry.getValue();
					Double oldUptime = mProcUptimes.get(proc);

					mProcUptimes.put(proc, (oldUptime + newUptime));
				}
			}
		}

		if (mDumpVengeance) {
			if (mVengeanceSampleList == null) {
				mConfig = br.mConfig;
				mVengeanceSampleList = new LinkedList<double[]>();
			}
			mVengeanceSampleList.add(br.mVengeanceSamples);
		}

		if (mDumpWhiteDamage) {
			if (br.mWhiteDamageSamples == null) mDumpWhiteDamage = false;
			if (mWhiteDamageSampleList == null) {
				mConfig = br.mConfig;
				mWhiteDamageSampleList = new LinkedList<double[]>();
			}
			mWhiteDamageSampleList.add(br.mWhiteDamageSamples);
		}

		if (mDumpYellowDamage) {
			if (br.mYellowDamageSamples == null) mDumpYellowDamage = false;
			if (mYellowDamageSampleList == null) {
				mConfig = br.mConfig;
				mYellowDamageSampleList = new LinkedList<double[]>();
			}
			mYellowDamageSampleList.add(br.mYellowDamageSamples);
		}

		if (mDumpMagicDamage) {
			if (br.mMagicDamageSamples == null) mDumpMagicDamage = false;
			if (mMagicDamageSampleList == null) {
				mConfig = br.mConfig;
				mMagicDamageSampleList = new LinkedList<double[]>();
			}
			mMagicDamageSampleList.add(br.mMagicDamageSamples);
		}

		return this;
	}

	@Override
	public String toString() {
		StringBuilder tmp = new StringBuilder();

		int iterations = getIterations();

		Result res;

		if (mShouldReportMitigation) {
			res = getResult(Stat.MITIGATION_VALUE);
			tmp.append("Mitigation Value: ").append(round(res.getMean())).append(" +/- ").append(round(res.getStandardError() * 1.96)).append('\n');
			tmp.append('\n');
		}

		res = getResult(Stat.THREAT_PER_SECOND);
		tmp.append("TPS: ").append(round(res.getMean())).append(" +/- ").append(round(res.getStandardError() * 1.96)).append('\n');
		tmp.append("StdDev: ").append(round(res.getStandardDeviation())).append('\n');
		tmp.append("DPS: ").append(round(getResult(Stat.DAMAGE_DEALT_PER_SECOND).getMean())).append('\n');
		tmp.append('\n');

		// Mitigation/Survivability
		tmp.append("Survivability Stats:").append('\n');
		tmp.append(" Demoralizing Shout uptime: ").append(round(getResult(ProtWarriorStat.DEMORALIZING_SHOUT_UPTIME).getMean())).append('%').append('\n');
		tmp.append(" Thunder Clap uptime: ").append(round(getResult(ProtWarriorStat.THUNDER_CLAP_UPTIME).getMean())).append('%').append('\n');
		tmp.append(" Shield Block uptime: ").append(round(getResult(ProtWarriorStat.SHIELD_BLOCK_UPTIME).getMean())).append('%').append('\n');
		if (getResult(ProtWarriorStat.TIER_12_4PC_UPTIME).getMax() > 0) {
			tmp.append(" 4T12 uptime: ").append(round(getResult(ProtWarriorStat.TIER_12_4PC_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.TIER_13_2PC_UPTIME).getMax() > 0) {
			tmp.append(" 2T13 uptime: ").append(round(getResult(ProtWarriorStat.TIER_13_2PC_UPTIME).getMean())).append('%').append('\n');
			tmp.append("  Damage absorbed: ").append(round(getResult(ProtWarriorStat.TIER_13_2PC_ABSORBED).getMean())).append('\n');
			tmp.append("  Average shield: ").append(round(getResult(ProtWarriorStat.TIER_13_2PC_AVG_SZ).getMean())).append('\n');
			tmp.append("  Procs: ").append(round(getResult(ProtWarriorStat.TIER_13_2PC_PROCS).getMean())).append('\n');
			tmp.append("  Proc interval: ").append(round(getResult(ProtWarriorStat.TIER_13_2PC_PROC_INTERVAL).getMean())).append(" sec").append('\n');
		}
		if (getResult(ProtWarriorStat.SHIELD_WALL_UPTIME).getMax() > 0) {
			tmp.append(" Shield Wall uptime: ").append(round(getResult(ProtWarriorStat.SHIELD_WALL_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.LAST_STAND_UPTIME).getMax() > 0) {
			tmp.append(" Last Stand uptime: ").append(round(getResult(ProtWarriorStat.LAST_STAND_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.ENRAGED_REGEN_UPTIME).getMax() > 0) {
			tmp.append(" Enraged Regeneration uptime: ").append(round(getResult(ProtWarriorStat.ENRAGED_REGEN_UPTIME).getMean())).append('%').append('\n');
			if (getResult(ProtWarriorStat.ENRAGED_REGEN_HPS).getMax() > 0) {
				tmp.append("  HPS: ").append(round(getResult(ProtWarriorStat.ENRAGED_REGEN_HPS).getMean())).append('\n');
				tmp.append("  Average heal: ").append(round(getResult(ProtWarriorStat.ENRAGED_REGEN_AVG_SZ).getMean())).append('\n');
			}
		}
		if (getResult(ProtWarriorStat.BLOOD_CRAZE_UPTIME).getMax() > 0) {
			tmp.append(" Blood Craze uptime: ").append(round(getResult(ProtWarriorStat.BLOOD_CRAZE_UPTIME).getMean())).append('%').append('\n');
			if (getResult(ProtWarriorStat.BLOOD_CRAZE_HPS).getMax() > 0) {
				tmp.append("  HPS: ").append(round(getResult(ProtWarriorStat.BLOOD_CRAZE_HPS).getMean())).append('\n');
				tmp.append("  Average heal: ").append(round(getResult(ProtWarriorStat.BLOOD_CRAZE_AVG_SZ).getMean())).append('\n');
			}
		}
		if (getResult(ProtWarriorStat.VICTORY_RUSH_HPS).getMax() > 0) {
			tmp.append(" Victory Rush:").append('\n');
			tmp.append("  HPS: ").append(round(getResult(ProtWarriorStat.VICTORY_RUSH_HPS).getMean())).append('\n');
		}
		tmp.append(" Hold the Line uptime: ").append(round(getResult(ProtWarriorStat.HOLD_THE_LINE_UPTIME).getMean())).append('%').append('\n');
		if (getResult(ProtWarriorStat.TOTAL_DTPS).getMax() > 0) {
			if (getResult(ProtWarriorStat.MOB_WHITE_ATTACK_DTPS).getMax() > 0 || getResult(ProtWarriorStat.MOB_YELLOW_ATTACK_DTPS).getMax() > 0) {
				tmp.append(" Physical damage reduction: ").append(round(getResult(ProtWarriorStat.PHYSICAL_DAMAGE_REDUCTION).getMean())).append('%').append('\n');
			}
			if (getResult(ProtWarriorStat.MOB_MAGIC_ATTACK_DTPS).getMax() > 0) {
				tmp.append(" Magic damage reduction: ").append(round(getResult(ProtWarriorStat.MAGIC_DAMAGE_REDUCTION).getMean())).append('%').append('\n');
			}
		}
		tmp.append('\n');

		if (getResult(ProtWarriorStat.TOTAL_DTPS).getMax() > 0) {
			tmp.append("Incoming Damage").append('\n');
			tmp.append(" DTPS: ").append(round(getResult(ProtWarriorStat.TOTAL_DTPS).getMean())).append('\n');
			if (getResult(ProtWarriorStat.MOB_WHITE_ATTACK_DTPS).getMax() > 0 && mHasValidMobWhiteDamage) {
				tmp.append(" White Attack: ").append('\n');
				tmp.append("  DTPS: ").append(round(getResult(ProtWarriorStat.MOB_WHITE_ATTACK_DTPS).getMean())).append('\n');
				tmp.append("  Nr. White Attacks: ").append(round(getResult(ProtWarriorStat.MOB_WHITE_ATTACKS).getMean())).append('\n');
				tmp.append("  Miss/Dodge/Parry/CritBlock/Block/Crit/Hit: ").
					append(round(getResult(ProtWarriorStat.P_MOB_WHITE_MISS).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_WHITE_DODGE).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_WHITE_PARRY).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_WHITE_CRIT_BLOCK).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_WHITE_BLOCK).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_WHITE_CRIT).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_WHITE_HIT).getMean())).append('%').append('\n');
				tmp.append("  Interval: ").append(round(getResult(ProtWarriorStat.MOB_WHITE_INTERVAL).getMean())).append(" sec").append('\n');
				tmp.append("  Average damage: ").append(round(getResult(ProtWarriorStat.MOB_WHITE_AVG_DMG).getMean())).append('\n');
			}
			if (getResult(ProtWarriorStat.MOB_YELLOW_ATTACK_DTPS).getMax() > 0 && mHasValidMobWhiteDamage) {
				tmp.append(" Yellow Attack: ").append('\n');
				tmp.append("  DTPS: ").append(round(getResult(ProtWarriorStat.MOB_YELLOW_ATTACK_DTPS).getMean())).append('\n');
				tmp.append("  Nr. White Attacks: ").append(round(getResult(ProtWarriorStat.MOB_YELLOW_ATTACKS).getMean())).append('\n');
				tmp.append("  Miss/Dodge/Parry/CritBlock/Block/Hit: ").
					append(round(getResult(ProtWarriorStat.P_MOB_YELLOW_MISS).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_YELLOW_DODGE).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_YELLOW_PARRY).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_YELLOW_CRIT_BLOCK).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_YELLOW_BLOCK).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_YELLOW_HIT).getMean())).append('%').append('\n');
				tmp.append("  Interval: ").append(round(getResult(ProtWarriorStat.MOB_YELLOW_INTERVAL).getMean())).append(" sec").append('\n');
				tmp.append("  Average damage: ").append(round(getResult(ProtWarriorStat.MOB_YELLOW_AVG_DMG).getMean())).append('\n');
			}
			if (getResult(ProtWarriorStat.MOB_MAGIC_ATTACK_DTPS).getMax() > 0 && mHasValidMobMagicDamage) {
				tmp.append(" Magic Attack: ").append('\n');
				tmp.append("  DTPS: ").append(round(getResult(ProtWarriorStat.MOB_MAGIC_ATTACK_DTPS).getMean())).append('\n');
				tmp.append("  Nr. Magic Attacks: ").append(round(getResult(ProtWarriorStat.MOB_MAGIC_ATTACKS).getMean())).append('\n');
				tmp.append("  Miss/Hit: ").
					append(round(getResult(ProtWarriorStat.P_MOB_MAGIC_MISS).getMean())).append('%').append('/').
					append(round(getResult(ProtWarriorStat.P_MOB_MAGIC_HIT).getMean())).append('%').append('\n');
				tmp.append("  Interval: ").append(round(getResult(ProtWarriorStat.MOB_MAGIC_INTERVAL).getMean())).append(" sec").append('\n');
				tmp.append("  Average damage: ").append(round(getResult(ProtWarriorStat.MOB_MAGIC_AVG_DMG).getMean())).append('\n');
			}
			tmp.append('\n');
		}

		// Pummel
		if (getResult(ProtWarriorStat.NR_TARGET_CASTS).getMax() > 0) {
			tmp.append("Interrupt Stats:").append('\n');
			tmp.append(" Interuptable casts: ").append(round(getResult(ProtWarriorStat.NR_TARGET_CASTS).getMean())).append('\n');
			tmp.append(" Nr. Pummels: ").append(round(getResult(ProtWarriorStat.NR_PUMMELS).getMean())).append('\n');
			if (getResult(ProtWarriorStat.NR_GAG_ORDERS).getMax() > 0) {
				tmp.append(" Nr. Gag Orders: ").append(round(getResult(ProtWarriorStat.NR_GAG_ORDERS).getMean())).append('\n');
			}
			tmp.append(" Interrupted casts: ").append(round(getResult(ProtWarriorStat.NR_INTERRUPTED_CASTS).getMean())).append('\n');
			tmp.append('\n');
		}

		// Threat Boosts
		tmp.append("Threat Boosts:").append('\n');
		tmp.append(" Vengeance:").append('\n');
		tmp.append("  Vengeance cap: ").append(round(getResult(ProtWarriorStat.VENGEANCE_CAP).getMean())).append('\n');
		tmp.append("  Vengeance AP: ").append(round(getResult(ProtWarriorStat.VENGEANCE_AVG).getMean())).append('\n');
		tmp.append(" Sunder Armor uptime: ").append(round(getResult(ProtWarriorStat.SUNDER_ARMOR_UPTIME).getMean())).append('%').append('\n');
		if (getResult(ProtWarriorStat.BASTION_OF_DEFENSE_UPTIME).getMax() > 0) {
			tmp.append(" Bastion of Defense uptime: ").append(round(getResult(ProtWarriorStat.BASTION_OF_DEFENSE_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.INNER_RAGE_UPTIME).getMax() > 0) {
			tmp.append(" Inner Rage uptime: ").append(round(getResult(ProtWarriorStat.INNER_RAGE_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.RECKLESSNESS_UPTIME).getMax() > 0) {
			tmp.append(" Recklessness uptime: ").append(round(getResult(ProtWarriorStat.RECKLESSNESS_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.RETALIATION_UPTIME).getMax() > 0) {
			tmp.append(" Retaliation uptime: ").append(round(getResult(ProtWarriorStat.RETALIATION_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.RUDE_INTERRUPTION_UPTIME).getMax() > 0) {
			tmp.append(" Rude Interruption uptime: ").append(round(getResult(ProtWarriorStat.RUDE_INTERRUPTION_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.TIER_12_DPS_2PC_UPTIME).getMax() > 0) {
			tmp.append(" 2T12 DPS uptime: ").append(round(getResult(ProtWarriorStat.TIER_12_DPS_2PC_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.HEROISM_UPTIME).getMax() > 0) {
			tmp.append(" Heroism uptime: ").append(round(getResult(ProtWarriorStat.HEROISM_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.TOTT_UPTIME).getMax() > 0) {
			tmp.append(" Tricks of the Trade uptime: ").append(round(getResult(ProtWarriorStat.TOTT_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.UNHOLY_FRENZY_UPTIME).getMax() > 0) {
			tmp.append(" Unholy Frenzy uptime: ").append(round(getResult(ProtWarriorStat.UNHOLY_FRENZY_UPTIME).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.SHATTERING_THROW_UPTIME).getMax() > 0) {
			tmp.append(" Shattering Throw uptime: ").append(round(getResult(ProtWarriorStat.SHATTERING_THROW_UPTIME).getMean())).append('%').append('\n');
		}
		tmp.append('\n');

		// Rage
		tmp.append("Rage:").append('\n');
		tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.RAGE_GAIN_TPS).getMean())).append('\n');
		tmp.append(" Rage gained: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED).getMean())).append('\n');
		tmp.append("  Auto attack: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED_AUTO_ATTACK).getMean())).append('\n');
		tmp.append("  Battle Shout/Commanding Shout: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED_SHOUT).getMean())).append('\n');
		if (getResult(ProtWarriorStat.RAGE_GAINED_BERSERKER_RAGE).getMax() > 0) {
			tmp.append("  Berserker Rage: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED_BERSERKER_RAGE).getMean())).append('\n');
		}
		tmp.append("  Charge: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED_CHARGE).getMean())).append('\n');
		if (getResult(ProtWarriorStat.RAGE_GAINED_SHIELD_SPECIALIZATION).getMax() > 0) {
			tmp.append("  Shield Specialization: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED_SHIELD_SPECIALIZATION).getMean())).append('\n');
		}
		if (getResult(ProtWarriorStat.RAGE_GAINED_MOB_WHITE).getMax() > 0) {
			tmp.append("  Mob Melee: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED_MOB_WHITE).getMean())).append('\n');
		}
		if (getResult(ProtWarriorStat.RAGE_GAINED_MOB_YELLOW).getMax() > 0) {
			tmp.append("  Mob Yellow: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED_MOB_YELLOW).getMean())).append('\n');
		}
		if (getResult(ProtWarriorStat.RAGE_GAINED_MOB_MAGIC).getMax() > 0) {
			tmp.append("  Mob Magic: ").append(round(getResult(ProtWarriorStat.RAGE_GAINED_MOB_MAGIC).getMean())).append('\n');
		}
		tmp.append(" Rage spent: ").append(round(getResult(ProtWarriorStat.RAGE_SPENT).getMean())).append('\n');
		if (getResult(ProtWarriorStat.RAGE_OVERFLOW).getMax() > 0 && mHasValidRageOverflow) {
			tmp.append(" Rage Overflow: ").append(round(getResult(ProtWarriorStat.RAGE_OVERFLOW).getMean())).append('\n');
		}
		if (getResult(ProtWarriorStat.BATTLE_TRANCE_PROCS).getMax() > 0) {
			tmp.append(" Battle Trance Procs: ").append(round(getResult(ProtWarriorStat.BATTLE_TRANCE_PROCS).getMean())).append('\n');
			tmp.append("  Average Rage return: ").append(round(getResult(ProtWarriorStat.BATTLE_TRANCE_AVG_RAGE_RETURN).getMean())).append('\n');
		}
		tmp.append('\n');

		// Ability Overview
		tmp.append("Ability Overview:").append('\n');
		if (getResult(ProtWarriorStat.P_RAGE_TPS).getMax() > 0) {
			tmp.append(" Rage gain threat: ").append(round(getResult(ProtWarriorStat.P_RAGE_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_WHITE_ATTACK_TPS).getMax() > 0) {
			tmp.append(" White Attack threat: ").append(round(getResult(ProtWarriorStat.P_WHITE_ATTACK_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_DEEP_WOUNDS_TPS).getMax() > 0) {
			tmp.append(" Deep Wounds threat: ").append(round(getResult(ProtWarriorStat.P_DEEP_WOUNDS_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_SHIELD_SLAM_TPS).getMax() > 0) {
			tmp.append(" Shield Slam threat: ").append(round(getResult(ProtWarriorStat.P_SHIELD_SLAM_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_DEVASTATE_TPS).getMax() > 0) {
			tmp.append(" Devastate threat: ").append(round(getResult(ProtWarriorStat.P_DEVASTATE_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_REVENGE_TPS).getMax() > 0) {
			tmp.append(" Revenge threat: ").append(round(getResult(ProtWarriorStat.P_REVENGE_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_THUNDER_CLAP_TPS).getMax() > 0) {
			tmp.append(" Thunder Clap threat: ").append(round(getResult(ProtWarriorStat.P_THUNDER_CLAP_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_REND_TPS).getMax() > 0) {
			tmp.append(" Rend threat: ").append(round(getResult(ProtWarriorStat.P_REND_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_CONCUSSION_BLOW_TPS).getMax() > 0) {
			tmp.append(" Concussion Blow threat: ").append(round(getResult(ProtWarriorStat.P_CONCUSSION_BLOW_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_SHOCKWAVE_TPS).getMax() > 0) {
			tmp.append(" Shockwave threat: ").append(round(getResult(ProtWarriorStat.P_SHOCKWAVE_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_HEROIC_THROW_TPS).getMax() > 0) {
			tmp.append(" Heroic Throw threat: ").append(round(getResult(ProtWarriorStat.P_HEROIC_THROW_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_VICTORY_RUSH_TPS).getMax() > 0) {
			tmp.append(" Victory Rush threat: ").append(round(getResult(ProtWarriorStat.P_VICTORY_RUSH_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_HEROIC_STRIKE_TPS).getMax() > 0) {
			tmp.append(" Heroic Strike threat: ").append(round(getResult(ProtWarriorStat.P_HEROIC_STRIKE_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_CLEAVE_TPS).getMax() > 0) {
			tmp.append(" Cleave threat: ").append(round(getResult(ProtWarriorStat.P_CLEAVE_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_RETALIATION_TPS).getMax() > 0) {
			tmp.append(" Retaliation threat: ").append(round(getResult(ProtWarriorStat.P_RETALIATION_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_COMBUST_TPS).getMax() > 0) {
			tmp.append(" Combust threat: ").append(round(getResult(ProtWarriorStat.P_COMBUST_TPS).getMean())).append('%').append('\n');
		}
		if (getResult(ProtWarriorStat.P_OTHER_TPS).getMax() > 0) {
			tmp.append(" Other threat: ").append(round(getResult(ProtWarriorStat.P_OTHER_TPS).getMean())).append('%').append('\n');
		}
		tmp.append('\n');

		// White Attack
		tmp.append("White Attack:").append('\n');
		tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.WHITE_ATTACK_TPS).getMean())).append('\n');
		tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.WHITE_ATTACK_DPS).getMean())).append('\n');
		tmp.append(" Nr. White Attacks: ").append(round(getResult(ProtWarriorStat.WHITE_ATTACKS).getMean())).append('\n');
		tmp.append(" Miss/Dodge/Parry/Glance/Crit/Hit: ").
			append(round(getResult(ProtWarriorStat.P_WHITE_ATTACK_MISS).getMean())).append('%').append('/').
			append(round(getResult(ProtWarriorStat.P_WHITE_ATTACK_DODGE).getMean())).append('%').append('/').
			append(round(getResult(ProtWarriorStat.P_WHITE_ATTACK_PARRY).getMean())).append('%').append('/').
			append(round(getResult(ProtWarriorStat.P_WHITE_ATTACK_GLANCE).getMean())).append('%').append('/').
			append(round(getResult(ProtWarriorStat.P_WHITE_ATTACK_CRIT).getMean())).append('%').append('/').
			append(round(getResult(ProtWarriorStat.P_WHITE_ATTACK_HIT).getMean())).append('%').append('\n');
		tmp.append(" Interval: ").append(round(getResult(ProtWarriorStat.WHITE_ATTACK_INTERVAL).getMean())).append(" sec").append('\n');
		tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.WHITE_ATTACK_AVG_DMG).getMean())).append('\n');
		tmp.append('\n');

		// Deep Wounds
		if (getResult(ProtWarriorStat.DEEP_WOUNDS_TPS).getMax() > 0) {
			tmp.append("Deep Wounds:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_DPS).getMean())).append('\n');
			tmp.append(" Nr. ticks: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_TICKS).getMean())).append('\n');
			if (getResult(ProtWarriorStat.DEEP_WOUNDS_ROLLED).getMax() > 0) {
				tmp.append(" Nr. rolled: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_ROLLED).getMean())).append('\n');
				tmp.append("  Rolled damage: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_ROLLED_DMG).getMean())).append('\n');
			}
			if (getResult(ProtWarriorStat.DEEP_WOUNDS_MUNCHED).getMax() > 0) {
				tmp.append(" Nr. munched: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_MUNCHED).getMean())).append('\n');
				tmp.append("  Munched damage: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_MUNCHED_DMG).getMean())).append('\n');
			}
			if (getResult(ProtWarriorStat.DEEP_WOUNDS_REFRESH_LOST).getMax() > 0) {
				tmp.append(" Nr. refreshes Lost: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_REFRESH_LOST).getMean())).append('\n');
				tmp.append("  Refresh lost damage: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_REFRESH_LOST_DMG).getMean())).append('\n');
			}
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.DEEP_WOUNDS_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Shield Slam
		// Note: Technically I could report Sword and Board metrics even if the use didn't Shield Slam, but what's the point?
		if (getResult(ProtWarriorStat.SHIELD_SLAM_TPS).getMax() > 0) {
			tmp.append("Shield Slam:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.SHIELD_SLAM_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.SHIELD_SLAM_DPS).getMean())).append('\n');
			tmp.append(" TPR: ").append(round(getResult(ProtWarriorStat.SHIELD_SLAM_TPR).getMean())).append('\n');
			tmp.append(" Nr. Shield Slams: ").append(round(getResult(ProtWarriorStat.SHIELD_SLAMS).getMean())).append('\n');
			tmp.append(" Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_SHIELD_SLAM_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_SHIELD_SLAM_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_SHIELD_SLAM_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_SHIELD_SLAM_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_SHIELD_SLAM_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.SHIELD_SLAM_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.SHIELD_SLAM_AVG_THREAT).getMean())).append('\n');
			if (getResult(ProtWarriorStat.SWORD_AND_BOARD_PROCS).getMax() > 0) {
				tmp.append(" Sword and Board Proc:").append('\n');
				tmp.append("  Nr. Sword and Board Procs: ").append(round(getResult(ProtWarriorStat.SWORD_AND_BOARD_PROCS).getMean())).append('\n');
				tmp.append("  Nr. Shield Slams: ").append(round(getResult(ProtWarriorStat.SWORD_AND_BOARD_PROC_SHIELD_SLAMS).getMean())).append('\n');
				tmp.append("  Nr. Procs wasted: ").append(round(getResult(ProtWarriorStat.SWORD_AND_BOARD_PROCS_WASTED).getMean())).append('\n');
			}
			tmp.append('\n');
		}

		// Devastate
		if (getResult(ProtWarriorStat.DEVASTATE_TPS).getMax() > 0) {
			tmp.append("Devastate:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.DEVASTATE_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.DEVASTATE_DPS).getMean())).append('\n');
			tmp.append(" TPR: ").append(round(getResult(ProtWarriorStat.DEVASTATE_TPR).getMean())).append('\n');
			tmp.append(" Nr. Devastates: ").append(round(getResult(ProtWarriorStat.DEVASTATES).getMean())).append('\n');
			tmp.append(" Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_DEVASTATE_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_DEVASTATE_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_DEVASTATE_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_DEVASTATE_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_DEVASTATE_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.DEVASTATE_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.DEVASTATE_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Revenge
		if (getResult(ProtWarriorStat.REVENGE_TPS).getMax() > 0) {
			tmp.append("Revenge:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.REVENGE_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.REVENGE_DPS).getMean())).append('\n');
			tmp.append(" TPR: ").append(round(getResult(ProtWarriorStat.REVENGE_TPR).getMean())).append('\n');
			tmp.append(" Nr. Revenges: ").append(round(getResult(ProtWarriorStat.REVENGES).getMean())).append('\n');
			tmp.append(" Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_REVENGE_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REVENGE_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REVENGE_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REVENGE_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REVENGE_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.REVENGE_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.REVENGE_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Thunder Clap
		if (getResult(ProtWarriorStat.THUNDER_CLAP_TPS).getMax() > 0) {
			tmp.append("Thunder Clap:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.THUNDER_CLAP_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.THUNDER_CLAP_DPS).getMean())).append('\n');
			tmp.append(" TPR: ").append(round(getResult(ProtWarriorStat.THUNDER_CLAP_TPR).getMean())).append('\n');
			tmp.append(" Nr. Thunder Claps: ").append(round(getResult(ProtWarriorStat.THUNDER_CLAPS).getMean())).append('\n');
			tmp.append(" Miss/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_THUNDER_CLAP_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_THUNDER_CLAP_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_THUNDER_CLAP_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.THUNDER_CLAP_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.THUNDER_CLAP_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Rend
		if (getResult(ProtWarriorStat.REND_TPS).getMax() > 0) {
			tmp.append("Rend:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.REND_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.REND_DPS).getMean())).append('\n');
			tmp.append(" Direct Damage:").append('\n');
			tmp.append("  TPS: ").append(round(getResult(ProtWarriorStat.REND_DIRECT_TPS).getMean())).append('\n');
			tmp.append("  DPS: ").append(round(getResult(ProtWarriorStat.REND_DIRECT_DPS).getMean())).append('\n');
			tmp.append("  TPR: ").append(round(getResult(ProtWarriorStat.REND_TPR).getMean())).append('\n');
			tmp.append("  Nr. Rends: ").append(round(getResult(ProtWarriorStat.RENDS).getMean())).append('\n');
			tmp.append("  Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_REND_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REND_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REND_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REND_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REND_HIT).getMean())).append('%').append('\n');
			tmp.append("  Average damage: ").append(round(getResult(ProtWarriorStat.REND_AVG_DMG).getMean())).append('\n');
			tmp.append("  Average threat: ").append(round(getResult(ProtWarriorStat.REND_AVG_THREAT).getMean())).append('\n');
			tmp.append(" DoT:").append('\n');
			tmp.append("  TPS: ").append(round(getResult(ProtWarriorStat.REND_DOT_TPS).getMean())).append('\n');
			tmp.append("  DPS: ").append(round(getResult(ProtWarriorStat.REND_DOT_DPS).getMean())).append('\n');
			tmp.append("  Nr. ticks: ").append(round(getResult(ProtWarriorStat.REND_TICKS).getMean())).append('\n');
			tmp.append("  Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_REND_DOT_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_REND_DOT_HIT).getMean())).append('%').append('\n');
			tmp.append("  Average damage: ").append(round(getResult(ProtWarriorStat.REND_DOT_AVG_DMG).getMean())).append('\n');
			tmp.append("  Average threat: ").append(round(getResult(ProtWarriorStat.REND_DOT_AVG_THREAT).getMean())).append('\n');
			tmp.append(" Uptime: ").append(round(getResult(ProtWarriorStat.REND_UPTIME).getMean())).append('%').append('\n');
			tmp.append('\n');
		}

		// Concussion Blow
		if (getResult(ProtWarriorStat.CONCUSSION_BLOW_TPS).getMax() > 0) {
			tmp.append("Concussion Blow:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.CONCUSSION_BLOW_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.CONCUSSION_BLOW_DPS).getMean())).append('\n');
			tmp.append(" TPR: ").append(round(getResult(ProtWarriorStat.CONCUSSION_BLOW_TPR).getMean())).append('\n');
			tmp.append(" Nr. Concussion Blows: ").append(round(getResult(ProtWarriorStat.CONCUSSION_BLOWS).getMean())).append('\n');
			tmp.append(" Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_CONCUSSION_BLOW_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_CONCUSSION_BLOW_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_CONCUSSION_BLOW_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_CONCUSSION_BLOW_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_CONCUSSION_BLOW_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.CONCUSSION_BLOW_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.CONCUSSION_BLOW_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Shockwave
		if (getResult(ProtWarriorStat.SHOCKWAVE_TPS).getMax() > 0) {
			tmp.append("Shockwave:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.SHOCKWAVE_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.SHOCKWAVE_DPS).getMean())).append('\n');
			tmp.append(" TPR: ").append(round(getResult(ProtWarriorStat.SHOCKWAVE_TPR).getMean())).append('\n');
			tmp.append(" Nr. Shockwaves: ").append(round(getResult(ProtWarriorStat.SHOCKWAVES).getMean())).append('\n');
			tmp.append(" Miss/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_SHOCKWAVE_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_SHOCKWAVE_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_SHOCKWAVE_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.SHOCKWAVE_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.SHOCKWAVE_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Heroic Throw
		if (getResult(ProtWarriorStat.HEROIC_THROW_TPS).getMax() > 0) {
			tmp.append("Heroic Throw:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.HEROIC_THROW_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.HEROIC_THROW_DPS).getMean())).append('\n');
			tmp.append(" Nr. Heroic Throws: ").append(round(getResult(ProtWarriorStat.HEROIC_THROWS).getMean())).append('\n');
			tmp.append(" Miss/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_HEROIC_THROW_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_HEROIC_THROW_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_HEROIC_THROW_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.HEROIC_THROW_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.HEROIC_THROW_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Victory Rush
		// Note: Technically I could report Impending Victory metrics even if the use didn't Victory Rush, but what's the point?
		if (getResult(ProtWarriorStat.VICTORY_RUSH_TPS).getMax() > 0) {
			tmp.append("Victory Rush:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.VICTORY_RUSH_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.VICTORY_RUSH_DPS).getMean())).append('\n');
			tmp.append(" Nr. Victory Rushes: ").append(round(getResult(ProtWarriorStat.VICTORY_RUSHS).getMean())).append('\n');
			tmp.append(" Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_VICTORY_RUSH_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_VICTORY_RUSH_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_VICTORY_RUSH_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_VICTORY_RUSH_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_VICTORY_RUSH_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.VICTORY_RUSH_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average heal: ").append(round(getResult(ProtWarriorStat.VICTORY_RUSH_AVG_HEAL).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.VICTORY_RUSH_AVG_THREAT).getMean())).append('\n');
			tmp.append(" Impending Victory Proc:").append('\n');
			tmp.append("  Nr. Impending Victory Procs: ").append(round(getResult(ProtWarriorStat.IMPENDING_VICTORY_PROCS).getMean())).append('\n');
			tmp.append("  Nr. Procs wasted: ").append(round(getResult(ProtWarriorStat.IMPENDING_VICTORY_PROCS_WASTED).getMean())).append('\n');
			tmp.append('\n');
		}

		// Heroic Strike
		if (getResult(ProtWarriorStat.HEROIC_STRIKE_TPS).getMax() > 0) {
			tmp.append("Heroic Strike:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.HEROIC_STRIKE_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.HEROIC_STRIKE_DPS).getMean())).append('\n');
			tmp.append(" TPR: ").append(round(getResult(ProtWarriorStat.HEROIC_STRIKE_TPR).getMean())).append('\n');
			tmp.append(" Nr. Heroic Strikes: ").append(round(getResult(ProtWarriorStat.HEROIC_STRIKES).getMean())).append('\n');
			tmp.append(" Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_HEROIC_STRIKE_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_HEROIC_STRIKE_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_HEROIC_STRIKE_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_HEROIC_STRIKE_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_HEROIC_STRIKE_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.HEROIC_STRIKE_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.HEROIC_STRIKE_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Cleave
		if (getResult(ProtWarriorStat.CLEAVE_TPS).getMax() > 0) {
			tmp.append("Cleave:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.CLEAVE_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.CLEAVE_DPS).getMean())).append('\n');
			tmp.append(" TPR: ").append(round(getResult(ProtWarriorStat.CLEAVE_TPR).getMean())).append('\n');
			tmp.append(" Nr. Cleave: ").append(round(getResult(ProtWarriorStat.CLEAVES).getMean())).append('\n');
			tmp.append(" Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_CLEAVE_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_CLEAVE_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_CLEAVE_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_CLEAVE_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_CLEAVE_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.CLEAVE_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.CLEAVE_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Retaliation
		if (getResult(ProtWarriorStat.RETALIATION_TPS).getMax() > 0) {
			tmp.append("Retaliation:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.RETALIATION_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.RETALIATION_DPS).getMean())).append('\n');
			tmp.append(" Nr. Retaliation: ").append(round(getResult(ProtWarriorStat.RETALIATIONS).getMean())).append('\n');
			tmp.append(" Miss/Dodge/Parry/Crit/Hit: ").
				append(round(getResult(ProtWarriorStat.P_RETALIATION_MISS).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_RETALIATION_DODGE).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_RETALIATION_PARRY).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_RETALIATION_CRIT).getMean())).append('%').append('/').
				append(round(getResult(ProtWarriorStat.P_RETALIATION_HIT).getMean())).append('%').append('\n');
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.RETALIATION_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.RETALIATION_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Combust (Tier 12 2pc)
		if (getResult(ProtWarriorStat.COMBUST_TPS).getMax() > 0) {
			tmp.append("Combust:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.COMBUST_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.COMBUST_DPS).getMean())).append('\n');
			tmp.append(" Nr. ticks: ").append(round(getResult(ProtWarriorStat.COMBUST_TICKS).getMean())).append('\n');
			if (getResult(ProtWarriorStat.COMBUST_ROLLED).getMax() > 0) {
				tmp.append(" Nr. rolled: ").append(round(getResult(ProtWarriorStat.COMBUST_ROLLED).getMean())).append('\n');
				tmp.append("  Rolled damage: ").append(round(getResult(ProtWarriorStat.COMBUST_ROLLED_DMG).getMean())).append('\n');
			}
			if (getResult(ProtWarriorStat.COMBUST_MUNCHED).getMax() > 0) {
				tmp.append(" Nr. munched: ").append(round(getResult(ProtWarriorStat.COMBUST_MUNCHED).getMean())).append('\n');
				tmp.append("  Munched damage: ").append(round(getResult(ProtWarriorStat.COMBUST_MUNCHED_DMG).getMean())).append('\n');
			}
			if (getResult(ProtWarriorStat.COMBUST_REFRESH_LOST).getMax() > 0) {
				tmp.append(" Nr. refreshes Lost: ").append(round(getResult(ProtWarriorStat.COMBUST_REFRESH_LOST).getMean())).append('\n');
				tmp.append("  Refresh lost damage: ").append(round(getResult(ProtWarriorStat.COMBUST_REFRESH_LOST_DMG).getMean())).append('\n');
			}
			tmp.append(" Average damage: ").append(round(getResult(ProtWarriorStat.COMBUST_AVG_DMG).getMean())).append('\n');
			tmp.append(" Average threat: ").append(round(getResult(ProtWarriorStat.COMBUST_AVG_THREAT).getMean())).append('\n');
			tmp.append('\n');
		}

		// Other
		if (getResult(ProtWarriorStat.OTHER_TPS).getMax() > 0 || getResult(ProtWarriorStat.OTHER_DPS).getMax() > 0 || getResult(ProtWarriorStat.OTHER_HPS).getMax() > 0) {
			tmp.append("Other Sources:").append('\n');
			tmp.append(" TPS: ").append(round(getResult(ProtWarriorStat.OTHER_TPS).getMean())).append('\n');
			tmp.append(" DPS: ").append(round(getResult(ProtWarriorStat.OTHER_DPS).getMean())).append('\n');
			if (getResult(ProtWarriorStat.OTHER_HPS).getMax() > 0) tmp.append(" HPS: ").append(round(getResult(ProtWarriorStat.OTHER_HPS).getMean())).append('\n');
			tmp.append('\n');
		}

		// Trinkets/Procs
		// TODO: You know what would be cool?  If I sorted this.
		if (mProcUptimes != null && mProcUptimes.isEmpty() == false) {
			tmp.append("Trinkets/Procs:").append('\n');
			for (Map.Entry<ProcEffect, Double> entry : mProcUptimes.entrySet()) {
				ProcEffect proc = entry.getKey();
				Double uptime = entry.getValue();

				if (proc.buffDuration == 0)
					tmp.append(' ').append(proc.name).append(" procs: ").append(round(uptime / iterations)).append('\n');
				else
					tmp.append(' ').append(proc.name).append(" uptime: ").append(round(uptime / iterations * 100)).append('%').append('\n');
			}
		}

		// Dump Vengeance with the CSV dumper.
		if (mDumpVengeance) {
			mDumpVengeance = false;
			CSVDumper.dump("vengeance.csv", "Vengeance Dump:\nConfig: " + mConfig, mVengeanceSampleList);
		}

		// Dump White Damage with the CSV dumper.
		if (mDumpWhiteDamage) {
			mDumpWhiteDamage = false;
			CSVDumper.dump("whitedamage.csv", "White Damage Taken Dump:\nConfig: " + mConfig, mWhiteDamageSampleList);
		}

		// Dump Yellow Damage with the CSV dumper.
		if (mDumpYellowDamage) {
			mDumpYellowDamage = false;
			CSVDumper.dump("yellowdamage.csv", "Yellow Damage Taken Dump:\nConfig: " + mConfig, mYellowDamageSampleList);
		}

		// Dump Magic Damage with the CSV dumper.
		if (mDumpMagicDamage) {
			mDumpMagicDamage = false;
			CSVDumper.dump("magicdamage.csv", "Magic Damage Taken Dump:\nConfig: " + mConfig, mMagicDamageSampleList);
		}

		return tmp.toString();
	}
}
